// ============================================================================
//
// Copyright (C) 2006-2007 Dengues
//
// Google Group: http://groups.google.com/group/dengues
// QQ Group: 24885404
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// ============================================================================
package org.dengues.ui.web.browser;

import org.dengues.ui.web.browser.event.DenguesProgressEvent;
import org.dengues.ui.web.browser.event.DenguesProgressListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.internal.ole.win32.IDispatch;
import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.internal.win32.TCHAR;
import org.eclipse.swt.ole.win32.OLE;
import org.eclipse.swt.ole.win32.OleAutomation;
import org.eclipse.swt.ole.win32.OleControlSite;
import org.eclipse.swt.ole.win32.OleEvent;
import org.eclipse.swt.ole.win32.OleFrame;
import org.eclipse.swt.ole.win32.OleListener;
import org.eclipse.swt.ole.win32.Variant;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

/**
 * A simple browser class copied from <code>IE</code>. At present, we need only to get the document of a web page and
 * to get a image of the web page. The OleFrame can display a browser, and we can get Document Object Model when the
 * navigate finished. So we need just <code>ProgressListener</code>.
 * 
 * @author jliu
 */
class DenguesSWTBrowser extends Composite {

    // Constants for browser OLE listeners
    private static final int DocumentComplete = 0x103;

    private static final int NavigateComplete2 = 0xfc;

    private static final int ProgressChange = 0x6c;

    private static final int RegisterAsBrowser = 0x228;

    private static final String CLSID_SHELLEXPLORER1 = "{EAB22AC3-30C1-11CF-A7EB-0000C05BAE0B}";

    private OleFrame oleFrame;

    private OleControlSite controlSite;

    private OleAutomation webBrowser;

    // ole listeners arrays
    private DenguesProgressListener[] progressListeners;

    private int globalDispatch;

    private boolean documentCompleted = false;

    /**
     * Create a new <code>ZSwtBrowser</code>.
     * 
     * @param parent
     * @param style
     */
    public DenguesSWTBrowser(Composite parent, int style) {
        super(parent, style);
        progressListeners = new DenguesProgressListener[0];
        initBrowser();
    }

    /**
     * Create and initialize the browser by using OleFrame and OleAutomation.
     * 
     * @param parent
     */
    private void initBrowser() {
        oleFrame = new OleFrame(this, SWT.NONE);

        /*
         * Registry entry HKEY_CLASSES_ROOT\Shell.Explorer\CLSID indicates which version of Shell.Explorer to use by
         * default. We usually want to use this value because it typically points at the newest one that is available.
         * However it is possible for this registry entry to be changed by another application to point at some other
         * Shell.Explorer version. The Browser depends on the Shell.Explorer version being at least Shell.Explorer.2. If
         * it is detected in the registry to be Shell.Explorer.1 then change the progId that will be embedded to
         * explicitly specify Shell.Explorer.2.
         */
        String progId = "Shell.Explorer"; //$NON-NLS-1$
        TCHAR key = new TCHAR(0, "Shell.Explorer\\CLSID", true); //$NON-NLS-1$
        int[] phkResult = new int[1];
        if (OS.RegOpenKeyEx(OS.HKEY_CLASSES_ROOT, key, 0, OS.KEY_READ, phkResult) == 0) {
            int[] lpcbData = new int[1];
            int result = OS.RegQueryValueEx(phkResult[0], null, 0, null, (TCHAR) null, lpcbData);
            if (result == 0) {
                TCHAR lpData = new TCHAR(0, lpcbData[0] / TCHAR.sizeof);
                result = OS.RegQueryValueEx(phkResult[0], null, 0, null, lpData, lpcbData);
                if (result == 0) {
                    String clsid = lpData.toString(0, lpData.strlen());
                    if (clsid.equals(CLSID_SHELLEXPLORER1)) {
                        /*
                         * Shell.Explorer.1 is the default, ensure that Shell.Explorer.2 is available
                         */
                        key = new TCHAR(0, "Shell.Explorer.2", true); //$NON-NLS-1$
                        int[] phkResult2 = new int[1];
                        if (OS.RegOpenKeyEx(OS.HKEY_CLASSES_ROOT, key, 0, OS.KEY_READ, phkResult2) == 0) {
                            /* specify that Shell.Explorer.2 is to be used */
                            OS.RegCloseKey(phkResult2[0]);
                            progId = "Shell.Explorer.2"; //$NON-NLS-1$
                        }
                    }
                }
            }
            OS.RegCloseKey(phkResult[0]);
        }
        try {
            controlSite = new OleControlSite(oleFrame, SWT.NONE, progId);
        } catch (RuntimeException e) {
            dispose();
        }
        controlSite.doVerb(OLE.OLEIVERB_INPLACEACTIVATE);
        webBrowser = new OleAutomation(controlSite);

        // Resize
        addListener(SWT.Resize, new Listener() {

            public void handleEvent(Event event) {
                oleFrame.setBounds(getClientArea());
            }
        });

        // Dispose
        addListener(SWT.Dispose, new Listener() {

            public void handleEvent(Event event) {
                if (webBrowser != null) {
                    webBrowser.dispose();
                }
            }
        });

        connectListenersToSite();

        Variant variant = new Variant(true);
        webBrowser.setProperty(RegisterAsBrowser, variant);
        variant.dispose();

        variant = new Variant(false);
        int[] rgdispid = webBrowser.getIDsOfNames(new String[] { "RegisterAsDropTarget" }); //$NON-NLS-1$
        if (rgdispid != null)
            webBrowser.setProperty(rgdispid[0], variant);
        variant.dispose();
    }

    /**
     * Add OleListeners to OleControlSite
     */
    private void connectListenersToSite() {
        OleListener oleListener = new OleListener() {

            public void handleEvent(OleEvent event) {
                /* callbacks are asynchronous, auto could be disposed */
                if (webBrowser != null) {
                    switch (event.type) {
                    // Fired when the entire document is finished loading.
                    // If you refresh the page, this event isn't fired.
                    case DocumentComplete: {
                        if (documentCompleted) {
                            return;
                        }
                        Variant varResult = event.arguments[0];
                        IDispatch dispatch = varResult.getDispatch();
                        if (/* globalDispatch != 0 && */dispatch.getAddress() == globalDispatch) {
                            /* final document complete */
                            // globalDispatch = 0;
                            DenguesProgressEvent progressEvent = new DenguesProgressEvent(DenguesSWTBrowser.this);
                            progressEvent.display = oleFrame.getDisplay();
                            progressEvent.widget = oleFrame;
                            for (int i = 0; i < progressListeners.length; i++) {
                                progressListeners[i].completed(progressEvent);
                            }
                            documentCompleted = true;
                        }
                        break;
                    }
                        // Fired after navigation completes. This event
                        // corresponds to BeforeNavigate2.
                    case NavigateComplete2: {
                        Variant varResult = event.arguments[0];
                        IDispatch dispatch = varResult.getDispatch();
                        // if (globalDispatch == 0) {
                        globalDispatch = dispatch.getAddress();
                        // }
                        break;
                    }
                    case ProgressChange: {
                        Variant arg1 = event.arguments[0];
                        int nProgress = arg1.getType() != OLE.VT_I4 ? 0 : arg1.getInt(); // may
                        // be
                        // -1
                        Variant arg2 = event.arguments[1];
                        int nProgressMax = arg2.getType() != OLE.VT_I4 ? 0 : arg2.getInt();
                        DenguesProgressEvent newEvent = new DenguesProgressEvent(DenguesSWTBrowser.this);
                        newEvent.display = DenguesSWTBrowser.this.getDisplay();
                        newEvent.widget = DenguesSWTBrowser.this;
                        newEvent.current = nProgress;
                        newEvent.total = nProgressMax;
                        if (nProgress != -1) {
                            for (int i = 0; i < progressListeners.length; i++) {
                                progressListeners[i].changed(newEvent);
                            }
                        }
                        break;
                    }
                    }
                }
                /*
                 * Dispose all arguments passed in the OleEvent. This must be done to properly release any IDispatch
                 * reference that was automatically addRef'ed when constructing the OleEvent.
                 */
                Variant[] arguments = event.arguments;
                for (int i = 0; i < arguments.length; i++)
                    arguments[i].dispose();
            }
        };
        controlSite.addEventListener(DocumentComplete, oleListener);
        controlSite.addEventListener(NavigateComplete2, oleListener);
        controlSite.addEventListener(ProgressChange, oleListener);
    }

    /**
     * Set a URL to navigate the browser.
     * 
     * @param url
     * @return true if navigate successfully.
     */
    public boolean navigate(String url) {
        checkWidget();
        if (url == null) {
            return false;
        }
        int rgdispid[] = webBrowser.getIDsOfNames(new String[] { "Navigate", "URL", "TargetFrameName" });
        Variant rgvarg[] = new Variant[] { new Variant(url) };
        int rgdispidNamedArgs[] = new int[] { rgdispid[1] };
        Variant pVarResult = webBrowser.invoke(rgdispid[0], rgvarg, rgdispidNamedArgs);
        documentCompleted = false;
        return pVarResult != null && pVarResult.getType() == OLE.VT_EMPTY;
    }

    /**
     * Get document variant value of current navigating.
     * 
     * @return
     */
    public Variant getDocument() {
        int htmlDocId[] = webBrowser.getIDsOfNames(new String[] { "Document" });
        if (htmlDocId == null)
            return null;
        return webBrowser.getProperty(htmlDocId[0]);
    }

    /**
     * Add a new <code>ZProgressListener</code> for this browser.
     * 
     * @param listener
     */
    public void addProgressListener(DenguesProgressListener listener) {
        DenguesProgressListener[] newProgressListeners = new DenguesProgressListener[progressListeners.length + 1];
        System.arraycopy(progressListeners, 0, newProgressListeners, 0, progressListeners.length);
        progressListeners = newProgressListeners;
        progressListeners[progressListeners.length - 1] = listener;
    }

    /**
     * Remove <code>ZProgressListener</code> for this browser.
     * 
     * @param listener
     */
    public void removeProgressListener(DenguesProgressListener listener) {
        if (progressListeners.length == 0)
            return;
        int index = -1;
        for (int i = 0; i < progressListeners.length; i++) {
            if (listener == progressListeners[i]) {
                index = i;
                break;
            }
        }
        if (index == -1)
            return;
        if (progressListeners.length == 1) {
            progressListeners = new DenguesProgressListener[0];
            return;
        }
        DenguesProgressListener[] newProgressListeners = new DenguesProgressListener[progressListeners.length - 1];
        System.arraycopy(progressListeners, 0, newProgressListeners, 0, index);
        System.arraycopy(progressListeners, index + 1, newProgressListeners, index, progressListeners.length - index - 1);
        progressListeners = newProgressListeners;
    }

    /**
     * @return true is browser is busy.
     */
    public boolean isBusy() {
        int dsOfNames[] = webBrowser.getIDsOfNames(new String[] { "Busy" });
        Variant property = webBrowser.getProperty(dsOfNames[0]);
        return property.getBoolean();
    }

    /**
     * @return title of the web browser
     */
    public String getTitle() {
        int rgdispid[] = webBrowser.getIDsOfNames(new String[] { "LocationName" });
        Variant property = webBrowser.getProperty(rgdispid[0]);
        return property.getString();
    }

    /**
     * Set silent true or false
     * 
     * @param silent
     */
    public void setSilent(boolean silent) {
        int rgdispid[] = webBrowser.getIDsOfNames(new String[] { "Silent" });
        webBrowser.setProperty(rgdispid[0], new Variant[] { new Variant(silent) });
    }

    public String getUrl() {
        checkWidget();
        int rgdispid[] = webBrowser.getIDsOfNames(new String[] { "LocationURL" });
        Variant pVarResult = webBrowser.getProperty(rgdispid[0]);
        if (pVarResult == null || pVarResult.getType() != 8) {
            return "";
        } else {
            return pVarResult.getString();
        }
    }

    /**
     * Stop the navigating.
     */
    public void stop() {
        checkWidget();
        int[] rgdispid = webBrowser.getIDsOfNames(new String[] { "Stop" }); //$NON-NLS-1$
        webBrowser.invoke(rgdispid[0]);
    }

    /**
     * Refresh the browser.
     */
    public void refresh() {
        checkWidget();
        int rgdispid[] = webBrowser.getIDsOfNames(new String[] { "Refresh" });
        webBrowser.invoke(rgdispid[0]);
    }

    /**
     * @return the oleFrame
     */
    public OleFrame getOleFrame() {
        return oleFrame;
    }
}
